package com.hitqz.robot.dispatch.netty.listener;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.cache.Cache;
import com.hitqz.robot.api.business.dto.FileChunkDto;
import com.hitqz.robot.api.business.dto.RobotClientSummaryDto;
import com.hitqz.robot.api.business.dto.RobotPositionDto;
import com.hitqz.robot.api.business.dto.RobotSendClientMsgDto;
import com.hitqz.robot.api.business.entity.*;
import com.hitqz.robot.api.common.dto.info.ReleaseResourceDto;
import com.hitqz.robot.api.common.dto.info.RobotNavInfoDto;
import com.hitqz.robot.api.common.entity.RobotMapPos;
import com.hitqz.robot.api.dispatch.model.MessageDto;
import com.hitqz.robot.api.dispatch.model.RobotDispatchDto;
import com.hitqz.robot.api.dispatch.protocol.ProtocolEnum;
import com.hitqz.robot.api.dispatch.protocol.dto.*;
import com.hitqz.robot.biz.service.*;
import com.hitqz.robot.biz.websocket.endpoint.SubscriptionEndpoint;
import com.hitqz.robot.biz.websocket.endpoint.SubscriptionLogEndpoint;
import com.hitqz.robot.camera.dto.RealManArmStateDto;
import com.hitqz.robot.common.core.jackson.RobotJavaTimeModule;
import com.hitqz.robot.common.core.util.R;
import com.hitqz.robot.common.core.util.RedisUtils;
import com.hitqz.robot.common.file.core.FileProperties;
import com.hitqz.robot.dispatch.netty.ChannelCache;
import com.hitqz.robot.dispatch.netty.factory.AlgorithmFactory;
import com.hitqz.robot.dispatch.netty.service.DispatchService;
import com.hitqz.robot.dispatch.netty.util.JtsUtil;
import com.hitqz.robot.dispatch.netty.util.LockUtil;
import com.hitqz.robot.dispatch.netty.util.RobotReplyUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Polygon;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

@Component
@Slf4j
@AllArgsConstructor
public class QueryListener {

    private final FileProperties fileProperties;

    private final RobotService robotService;

    private final RobotMapService robotMapService;

    private final RobotMapPosService robotMapPosService;

    private final RobotMapAreaService robotMapAreaService;

    private final RobotMapAreaPosService robotMapAreaPosService;

    private final TicketsService ticketsService;

    private final TicketsTaskService ticketsTaskService;

    private final RobotWarningRecordService robotWarningRecordService;

    private final RobotGroupService robotGroupService;

    private final AlgorithmFactory algorithmFactory;

    private final BatteryDataInfoService batteryDataInfoService;

    private final DispatchService dispatchService;

    private final BigDecimal THOUSAND = new BigDecimal(2000);


    private static final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new RobotJavaTimeModule());
    public static final Map<String, Map<String, String>> topicClientMessages = new ConcurrentHashMap<>(16);

    private static final Map<String, String> CLIENT_MAP_SERVER_URL_MAP = new HashMap<>(8);

    //话题限流
    private static final Map<String, RateLimiter> topicRateLimiterMap = new ConcurrentHashMap<>(8);


    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotMapDto dto) {
        RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
        if (robotEntity == null) {
            log.info("[netty listener] robot is not registered");
        }
        RobotMapEntity robotMapEntity = robotMapService.findByMapCode(dto.getMapCode());
        if (robotMapEntity == null) {
            robotMapEntity = new RobotMapEntity();
        }
        if (StrUtil.isNotBlank(dto.getDownloadUrl())) {
            try {
                String suffix = Objects.equals(dto.getType(), 2) ? ".pcd" : ".png";
                String filePath = StrUtil.isBlank(robotMapEntity.getFilePath()) ?
                        Paths.get(fileProperties.getLocal().getBasePath(), "map", dto.getMapCode() + suffix).toFile().getAbsolutePath()
                        : Paths.get(fileProperties.getLocal().getBasePath(), robotMapEntity.getFilePath()).toString();
                String relativePath = filePath.replace(Paths.get(fileProperties.getLocal().getBasePath()).toString(), "").replaceAll("\\\\", "/");
                //if (StrUtil.isNotBlank(dto.getDownloadUrl())){
                //	try {
                //		log.info("map download url:{}",dto.getDownloadUrl());
                //		URL url = URLUtil.url(dto.getDownloadUrl());
                //		FileUtil.writeFromStream(URLUtil.getStream(url), filePath);
                //		robotMapEntity.setFilePath(relativePath);
                //		log.info("{}，更新地图文件 relativePath:{}", dto.getClientId(), relativePath);
                //	}catch (Exception e) {
                //		log.info("下载地图失败" +e.getMessage());
                //	}
                //
                //}
            } catch (Exception e) {
                log.error("sync map download error", e);
            }
        }
        BeanUtil.copyProperties(dto, robotMapEntity, false);
        robotMapService.saveOrUpdate(robotMapEntity);
        //清除旧缓存
        CLIENT_MAP_SERVER_URL_MAP.remove(Optional.of(dto).map(RobotMapDto::getMapCode).orElse("-"));
    }


    @EventListener
    @Async("serverMapPosListenerThreadPool")
    public void handleQuery(RobotMapPosDto dto) {
        synchronized (this) {
            RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
            if (robotEntity == null) {
                log.info("[netty listener] robot is not registered");
            }
            RobotMapPosEntity robotMapPos = robotMapPosService.getOne(Wrappers.<RobotMapPosEntity>lambdaQuery()
                    .eq(RobotMapPosEntity::getMapCode, dto.getMapCode())
                    .eq(RobotMapPosEntity::getCode, dto.getCode())
            );
            if (robotMapPos == null) {
                robotMapPos = new RobotMapPosEntity();
            }
            BeanUtil.copyProperties(dto, robotMapPos, false);
            robotMapPosService.saveOrUpdate(robotMapPos);
        }
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotMapAreaDto dto) throws JsonProcessingException {
        log.info("[netty listener RobotMapAreaDto]收到消息");
        synchronized (this) {
            RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
            if (robotEntity == null) {
                log.info("[netty listener] robot is not registered");
            } else {
                // 全部删除 然后重新添加
                RobotMapAreaEntity areaEntity = new RobotMapAreaEntity();
                BeanUtil.copyProperties(dto, areaEntity, false);
                robotMapAreaService.save(areaEntity);
                if (CollectionUtil.isNotEmpty(dto.getAreaPos())) {
                    for (RobotMapAreaPosDto robotMapAreaPosDto : dto.getAreaPos()) {
                        RobotMapAreaPosEntity posEntity = new RobotMapAreaPosEntity();
                        BeanUtil.copyProperties(robotMapAreaPosDto, posEntity);
                        posEntity.setAreaId(areaEntity.getId());
                        robotMapAreaPosService.save(posEntity);
                    }
                }
            }
        }

    }


    //高频事件 要避免重复查询，否则可能堆溢出
    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotClientSummaryDto dto) {
        try {
            //通过ws给前端广播消息
            // 保存下最新的信息
            Cache<String, RobotClientSummaryDto> posInfoDtoCache = SpringUtil.getBean("robotCache");
            if (Optional.of(dto).map(RobotClientSummaryDto::getPosition).isPresent()) {
                RobotPositionDto position = dto.getPosition();
                String mapCode = position.getMapCode();
                //替换地图 url 为 server 本地的
                if (StrUtil.isNotBlank(mapCode)) {
                    String mapUrl = CLIENT_MAP_SERVER_URL_MAP.get(mapCode);
                    if (mapUrl == null) {
                        RobotMapEntity robotMapEntity = robotMapService.findByMapCode(mapCode);
                        //存一个空避免重复查
                        mapUrl = Optional.ofNullable(robotMapEntity).map(RobotMapEntity::getFilePath).orElse("");
                        CLIENT_MAP_SERVER_URL_MAP.put(mapCode, Optional.ofNullable(robotMapEntity).map(RobotMapEntity::getFilePath).orElse(""));
                    }
                    position.setUrl(mapUrl);
                }
            }
            Cache<String, Polygon> robotPosPolygon = SpringUtil.getBean("robotPosPolygon");
            BigDecimal robotWidth = new BigDecimal(dto.getOtherInfo().getRobotWidth())
                    .divide(THOUSAND, 3, RoundingMode.HALF_UP);
            BigDecimal robotLength = new BigDecimal(dto.getOtherInfo().getRobotLength())
                    .divide(THOUSAND, 3, RoundingMode.HALF_UP);
            robotPosPolygon.put(dto.getClientId(),
                    JtsUtil.getRectangleVertices(dto.getPosition().getLaserPos().getX(),
                            dto.getPosition().getLaserPos().getY(),
                            robotLength.doubleValue(), robotWidth.doubleValue()));
            posInfoDtoCache.put(dto.getClientId(), dto);
            //
            RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
            if (robotEntity != null) {
                dto.setGroupId(robotEntity.getGroupId());
            }
            // 计算位置的区域


            String topic = "summary";//topic应该整理成枚举类
            topicClientMessages.computeIfAbsent(topic,
                    k -> new ConcurrentHashMap<>()).put(dto.getClientId(), objectMapper.writeValueAsString(dto));
            broadcastMessageByTopic(topic);


        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotDto dto) {
        RobotEntity robotEntity = robotService.selectByRobotSn(dto.getRobotSn());
        if (robotEntity == null) {
            robotEntity = new RobotEntity();
            robotEntity.setGroupId(1);

        }
        BeanUtil.copyProperties(dto, robotEntity, false);
        robotService.saveOrUpdate(robotEntity);
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(ReportLogMessageDto reportLogMessageDto) {
        SubscriptionLogEndpoint.broadcast(
                reportLogMessageDto.getRandomId() == null ? null : Integer.valueOf(reportLogMessageDto.getRandomId()),
                reportLogMessageDto.getClientId(), reportLogMessageDto.getMessage());
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(UploaderLogFileDto uploaderLogFileDto) {
        log.info("收到日志文件地址: {}---{}", uploaderLogFileDto.getClientId(), uploaderLogFileDto.getLogFile());
        Cache<String, String> posInfoDtoCache = SpringUtil.getBean("uploaderLogs");
        posInfoDtoCache.put(uploaderLogFileDto.getClientId(), uploaderLogFileDto.getLogFile());
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotApplyPathDto dto) {
        synchronized (this) {
            log.info("请求前往目标点:" + dto.getCode() + "---" + dto.getClientId());
            Cache<String, RobotDispatchDto> posInfoDtoCache = SpringUtil.getBean("cacheRobotTask");
            RobotDispatchDto robotDispatchDto = new RobotDispatchDto();
            robotDispatchDto.setClientId(dto.getClientId());
            robotDispatchDto.setPathCodes(dto.getPathList());
            robotDispatchDto.setTargetPosCode(dto.getCode());
            robotDispatchDto.setPathDistances(dto.getPathDistances());
            posInfoDtoCache.put(dto.getClientId(), robotDispatchDto);
            // 回复接受成功 暂时不需要回复回复直接加入调度组缓存里面进行计算
            // 考虑---先暂停队列，后续根据调度的计算判断是否要进行继续
            RobotSendClientMsgDto robotSendClientMsgDto = new RobotSendClientMsgDto();
            robotSendClientMsgDto.setCmd(0);
            robotSendClientMsgDto.setSeq(Integer.parseInt(
                    String.valueOf(ProtocolEnum.ROBOT_APPLY_TARGET_POS.getCmd())));
            robotSendClientMsgDto.setClientId(dto.getClientId());
            robotSendClientMsgDto.setData(R.ok());
            SpringUtil.getApplicationContext().publishEvent(robotSendClientMsgDto);
            // 加入调度的数据
            RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
            if (robotEntity != null) {
                RobotGroupEntity robotGroup = robotGroupService.getById(robotEntity.getGroupId());
                if (robotGroup == null || StrUtil.isEmpty(robotGroup.getDispatchAlgorithm())) {
                    log.info("robot has no DispatchAlgorithm or dispatch group {}", dto.getClientId());
                    return;
                }
                algorithmFactory.getRoutingStrategy(robotGroup.getDispatchAlgorithm()).changePath(robotGroup, dto);
            }
        }


    }


    @EventListener
    @Async("serverListenerThreadPool")
    public void handlerQuery(FormConfigModalDto dto) {
        //使用redis，而不用cache，避免重启server，而client没有连接，且要去修改旧任务，会没有数据。
        String jsonString = JSONObject.toJSONString(dto);
        RedisUtils.set("dynamicTaskContent:" + dto.getRobotSn(), jsonString);
        log.info("保存动态表单:{},{},{}", dto.getRobotSn(), dto.getFormTitle(), dto.getFormName());
    }


    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(RobotTicketsDto dto) {
        synchronized (this) {
            TicketsEntity tickets = ticketsService.getById(dto.getServerId());
            if (tickets == null) {
                tickets = ticketsService.getOne(Wrappers.<TicketsEntity>lambdaQuery().eq(TicketsEntity::getTaskNo, dto.getTaskNo()));
                if (tickets == null) {
                    tickets = new TicketsEntity();
                }
            }
            BeanUtil.copyProperties(dto, tickets);
            if (dto.getServerId() != null) {
                tickets.setId(dto.getServerId().longValue());
            }
            if (tickets.getId() != null && tickets.getId() > 0) {
                ticketsService.updateById(tickets);
            } else {
                ticketsService.save(tickets);
            }
            dto.setServerId(tickets.getId().intValue());
            if (CollectionUtil.isNotEmpty(dto.getTickets())) {
                for (RobotTicketsTaskDto taskDto : dto.getTickets()) {
                    TicketsTaskEntity ticketsTaskEntity = new TicketsTaskEntity();
                    if (taskDto.getServerId() != null) {
                        ticketsTaskEntity = ticketsTaskService.getById(taskDto.getServerId());
                    }
                    BeanUtil.copyProperties(taskDto, ticketsTaskEntity, false);
                    ticketsTaskEntity.setTicketsId(tickets.getId());

                    ticketsTaskService.saveOrUpdate(ticketsTaskEntity);
                    taskDto.setServerId(ticketsTaskEntity.getId().intValue());
                }
            }
            // 更新完成后，下发serverId
            MessageDto messageDto = new MessageDto();
            messageDto.setSn(0);
            messageDto.setClientId(dto.getAllotRobotSn());
            messageDto.setCmd(ProtocolEnum.ROBOT_TICKETS.getCmd());
            messageDto.setData(JSONObject.toJSONString(dto).getBytes(StandardCharsets.UTF_8));
            ChannelCache.send2Client(dto.getAllotRobotSn(),
                    BeanUtil.copyProperties(messageDto, MessageDto.class));
            // 开始设置，任务时间
            Cache<String, Long> idleTask = SpringUtil.getBean("idleTask");
            idleTask.put(dto.getAllotRobotSn(), System.currentTimeMillis());
        }
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handleQuery(MessageDto messageDto) throws IOException {
        ProtocolEnum protocolEnum = ProtocolEnum.CMD_PROTOCOL_MAP.get(messageDto.getCmd());
        //log.info("[netty listener]" + messageDto.getSn());
        String clientId = messageDto.getClientId();
        if (messageDto.getSn() > 0) {
            Lock lock = LockUtil.ROBOT_CMD_TICKETS_LOCKS.get(clientId, (short) messageDto.getSn());
            if (lock != null) {
                RobotReplyUtil.COMMON_REPLY_TABLE.put(clientId, (short) messageDto.getSn(),
                        JSONObject.parseObject(new String(messageDto.getData(), StandardCharsets.UTF_8), R.class));
                try {
                    lock.notifyAll();
                } catch (Exception e) {

                }
            }
        }

    }


    @EventListener
    @Async("serverListenerThreadPool")
    public void handlerQuery(RobotCmdNavDto robotCmdNavDto) {


    }


    //处理分块文件
    @EventListener
    @Async("serverFileThreadPool")
    public void handlerFile(FileChunkDto dto) {


    }


    @EventListener
    @Async("serverFileThreadPool")
    public void handlerBatteryLevel(ReportBatteryLevelDto dto) {
//        BatteryDataInfoEntity entity = new BatteryDataInfoEntity();
//        entity.setClientId(dto.getClientId());
//        entity.setStatisticalTime(dto.getStatisticalTime());
//        entity.setBattery(dto.getBattery());
//        entity.setCurrent(dto.getCurrent());
//        entity.setVoltage(dto.getVoltage());
//        entity.setStatisticalTime(dto.getStatisticalTime());
//        batteryDataInfoService.save(entity);
    }


    @EventListener
    @Async("serverListenerThreadPool")
    public void handlerWarning(RobotWarningDto robotWarningDto) {
        log.info("Robot Warning" + JSONObject.toJSONString(robotWarningDto));
        RobotWarningRecordEntity robotWarningRecord = new RobotWarningRecordEntity();
        robotWarningRecord.setFlag("0");
        robotWarningRecord.setContent(robotWarningDto.getContent());
        robotWarningRecord.setServiceId(robotWarningDto.getRobotSn());
        robotWarningRecord.setTime(LocalDateTime.now());
        robotWarningRecord.setTitle(robotWarningDto.getWarningObj());
        robotWarningRecord.setType("0");
        robotWarningRecord.setLevel(robotWarningDto.getLevel());
        robotWarningRecordService.save(robotWarningRecord);
    }

    @EventListener
    @Async("serverListenerThreadPool")
    public void handlerRealManArmState(RealManArmStateDto realManArmStateDto) throws Exception {
        String topic = "realManArmState";
        topicClientMessages.computeIfAbsent(topic,
                k -> new ConcurrentHashMap<>()).put("any", objectMapper.writeValueAsString(realManArmStateDto));
        broadcastMessageByTopic(topic);
    }

    public static void broadcastMessageByTopic(String topic) {
        //限流
        RateLimiter rateLimiter = topicRateLimiterMap.computeIfAbsent(topic, k -> RateLimiter.create(1));
        if (rateLimiter.tryAcquire()) {
            Map<String, String> clientMessages = topicClientMessages.getOrDefault(topic, Collections.emptyMap());
            // 创建广播的消息内容，包含所有去重后的消息
            String broadcastMessage = createBroadcastMessage(clientMessages);
            // 广播消息给所有订阅的会话
            if (topic.equals("summary")) {
                SubscriptionEndpoint.broadcast(topic, broadcastMessage);
            }
        }
    }

    private static String createBroadcastMessage(Map<String, String> clientMessages) {
        try {
            // 将去重后的消息转换成JSONArray
            ArrayNode messagesArray = objectMapper.createArrayNode();
            //按key的顺序去添加
            clientMessages.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
                try {
                    messagesArray.add(objectMapper.readTree(entry.getValue()));
                } catch (IOException e) {
                    log.error("[ws][publish]Failed to parse message: {}", e.getMessage());
                }
            });
            //转成不带换行符的json字符串
            return objectMapper.writeValueAsString(messagesArray);
        } catch (Exception e) {
            log.error("[ws][publish]Failed to create broadcast message: {}", e.getMessage());
            return "{\"error\":\"Failed to create broadcast message\"}";
        }
    }


    /**
     * 移除所有topic中指定执行端相关消息
     *
     * @param clientId
     */
    public static void removeClientMsgFromAllTopic(String clientId) {
        if (StringUtils.hasText(clientId)) {
            List<Map<String, String>> listMap = topicClientMessages.values().stream().toList();
            for (Map<String, String> map : listMap) {
                map.remove(clientId);
            }
        }
    }

    @EventListener
    @Async("serverHandleNavInfoThreadPool")
    public void handleRobotNavInfo(RobotNavInfoDto dto) {
        dispatchService.handle(dto);
    }


    @EventListener
    @Async("serverListenerThreadPool")
    public void releaseResource(ReleaseResourceDto dto) {
        dispatchService.releaseResource(dto);
        dispatchService.cancelGotoTarget(dto);
    }


    @EventListener
    @Async("applyGotoTargetThreadPool")
    public void applyGotoTarget(RobotApplyGotoTargetDto dto) {
        dispatchService.applyGotoTarget(dto);
    }
}
